<?php
ini_set( 'display_errors', '1' );
require_once __DIR__.'/vendor/autoload.php';
use Web3\Utils;
use Web3\Web3;
use Web3\Providers\HttpProvider;
use Web3\RequestManagers\HttpRequestManager;
use Web3p\EthereumTx\Transaction;
use Web3\Formatters\HexFormatter;
use Web3\Formatters\QuantityFormatter;
use Web3\Formatters\NumberFormatter;

use Zeeging\EthereumWallet;


/** 
* 处理使用Ethereum方式充值的逻辑的类
* 
* @author      Eugene
* @version     1.0
*/
Class EthereumApi{
	private $web3;
	const FILTER_RECEIVER=0;
	const FILTER_SENDER=1;
	const FILTER_BOTH=2;
	
	//参数1：访问的URL，参数2：post数据(不填则为GET)，参数3：提交的$cookies,参数4：是否返回$cookies
    static public function curl_request($url,$post='',$cookie='', $returnHeader=1 ,$headers=array()){
        $curl = curl_init();
        curl_setopt($curl, CURLOPT_URL, $url);
        curl_setopt($curl, CURLOPT_USERAGENT, 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0)');
        curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1);
        curl_setopt($curl, CURLOPT_AUTOREFERER, 1);
        curl_setopt($curl, CURLOPT_REFERER, "http://XXX");
        if($post) {
            curl_setopt($curl, CURLOPT_POST, 1);
            curl_setopt($curl, CURLOPT_POSTFIELDS, http_build_query($post));
        }
        if($cookie) {
            curl_setopt($curl, CURLOPT_COOKIE, $cookie);
        }
        curl_setopt($curl, CURLOPT_HEADER, $returnHeader);
        curl_setopt($curl, CURLOPT_TIMEOUT, 10);
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
        $data = curl_exec($curl);
        if (curl_errno($curl)) {
            return curl_error($curl);
        }
        $httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
        curl_close($curl);
        if($returnHeader){
            list($header, $body) = explode("\r\n\r\n", $data, 2);
            preg_match_all("/Set\-Cookie:([^;]*);/", $header, $matches);
            $info['cookie']  = isset($matches[1])?$matches[1]:null;
            $info['content'] = $body;
            $info['httpheader'] = explode("\r\n", $header);
            $info['httpcode'] = $httpCode;
            return $info;
        }else{
            return $data;
        }
    }

	public function __construct(){
		$currentlyNetwork='MAINNET';
		// $currentlyNetwork='ROPSTEN';
		$ropstenProvider='https://ropsten.infura.io/v3/8c2ce7fc647f4e7c9f16060d0fa3b34a';
		$mainnetProvider='https://mainnet.infura.io/v3/8c2ce7fc647f4e7c9f16060d0fa3b34a';
		$provider = $currentlyNetwork == 'MAINNET'? $mainnetProvider : $ropstenProvider ;
		$this->web3 = new Web3(new HttpProvider(new HttpRequestManager($provider,15)));
	}
	
	/**  
	* 生成一个新的钱包私钥和地址 
	* 
	* @access public 
	* @return object 以object返回钱包地址和私钥，结构为 { "address": "0xxxx", "private": "0xxxx" }
	*/  
	public function generateWallet(){
		return EthereumWallet::generate();
	}
	/**  
	* 读取最后N个blocks 
	* 
	* @access public 
	* @param int $offset 指定要读取最后多少个blocks
	* @return array 返回最后n个blocks的数组
	*/  
	public function loadLastBlocks($offset,$confirmationNumber=0){
		// 参数offset是指定要取最后多少个block

		$blockArray=array();
		$blockHeight=null;

		$eth=$this->web3->eth;

		$eth->blockNumber(function($err,$blockNumber) use($eth, $offset, $confirmationNumber, &$blockArray, &$blockHeight){
			// echo $blockNumber;
			$blockHeight=$blockNumber;
			//先取到区块链总区块数量
			if ($err !== null) {
		        // do something
		        echo 'ERR'.$err;
		        return;
		    }
		    if (isset($blockNumber)) {
		    	//整理输入值，如果没有指定从哪个区块开始向前查询，则默认从最后一个区块开始
		    	if(empty($endBlock) || $endBlock>$blockNumber){
					$endBlock=$blockNumber->subtract(new \phpseclib\Math\BigInteger($confirmationNumber));
				}
				//根据输入的向前查询区块数量，计算从哪个区块开始查询
				$startBlock=$endBlock->subtract(new \phpseclib\Math\BigInteger($offset));

				//echo "Searching block from $startBlock to $endBlock...";
				//开始根据定义好的查询范围遍历查询区块
				for($i=$startBlock;$i->compare($endBlock);$i=$i->add(new \phpseclib\Math\BigInteger(1))){
					//根据遍历的编号$i查询区块详细内容
					$eth->getBlockByNumber(QuantityFormatter::format($i),true,function($err,$block) use($i, &$blockArray){
						if($err !== null){
							echo 'Err'.$err;
							return ;
						}
						if(isset($block)){
							// echo "Block ".$i->toString()." Info: ";
							// print_r(json_encode($block));
							array_push($blockArray, $block);
							/*
							//遍历查询当前区块的所有交易
							foreach ($block->transactions as $key => $transaction) {
								//若找到从查询账号转出的交易，加入到转出交易数组
								if($transaction->from == $address){
									array_push($txFromAccount, $transaction);
								}
								//若找到从查询账号收到的交易，加入到收到交易数组
								if($transaction->to == $address){
									array_push($txToAccount, $transaction);
								}
							}
							*/
						} // 如果读取成功
					}); // 读取当前循环的block
				}	// 循环读取blocks
		    }	// if (isset($blockNumber)) {}
		});	// $eth->blockNumber();
		return (object)array("blockArray"=>$blockArray,"blockHeight"=>$blockHeight);
	}
	/**  
	* 过滤不相关的交易，返回相关的交易 
	* 
	* @access public 
	* @param array $userAddressArray 提供用户地址列表，将根据过滤方式过滤掉与这些地址不相关的交易
	* @param int $filterType 整形常量：
	* 			FILTER_REICIVER: 只返回接收地址与指定地址一致的
	* 			FILTER_SENDER: 只返回发送地址与指定地址一致的
	* 			FILTER_BOTH: 返回接收地址或发送地址与指定地址一致的
	* @return array 返回最后n个blocks的数组
	*/  
	public function txFilter($userAddressArray,$filterType,$lastBlocks=null){
		if(empty($lastBlocks)){
			$lastBlocks=$this->loadLastBlocks(12,1);
		}
		$blockArray=$lastBlocks->blockArray;
		$blockHeight=$lastBlocks->blockHeight;
		// print_r($blockArray);
		$txArray=array();
		// print_r(json_encode($blockArray));exit();
		foreach ($blockArray as $key => $block) {
			foreach ($block->transactions as $key => $tx) {
				// print_r(strtolower($tx->to));
				// print_r($userAddressArray);
				if( ( $filterType==self::FILTER_RECEIVER || $filterType==self::FILTER_BOTH ) && in_array(strtolower($tx->to), $userAddressArray)){
					array_push($txArray, $tx);
				}
				if( ( $filterType==self::FILTER_SENDER || $filterType==self::FILTER_BOTH ) && in_array(strtolower($tx->from), $userAddressArray)){
					array_push($txArray, $tx);
				}
			}
		}
		return (object)array("txArray"=>$txArray,"blockHeight"=>$blockHeight);
	}
	/**  
	* 读取一个transaction对象里的value，可以指定返回的单位
	* 
	* @access public
	* @param object $tx transaction对象
	* @param string $unit 返回的value值的单位，wei、ether及其它以太币单位，也可以是rmb，返回实时的人民币价值
	* @return string 以字符串形式返回value数值
	*/  
	static public function tx_getValue($tx,$unit){
		if($unit=='rmb'){
			$valueInEther=bcdiv(NumberFormatter::format($tx->value),'1000000000000000000',20);
			$pricePerEther=self::realtimePrice();
			return bcmul($pricePerEther,$valueInEther,20);
		}
		else{
			list($bnq, $bnr) = Utils::fromWei(Utils::toBn($tx->value), $unit);
			return $bnq->toString();
		}
	}
	/**  
	* 获取实时的ETH兑人民币价值
	* 
	* @access public
	* @return string 实时的ETH兑人民币价值
	*/  
	static public function realtimePrice(){
		$resp=json_decode(self::curl_request('https://widgets.coinmarketcap.com/v1/ticker/ethereum/?convert=CNY','','',0));
		return $resp[0]->price_cny;
	}
	/**  
	* 把人民币金额转化成实时的等值的以太币金额
	* 
	* @access public
	* @param float $amount 输入人民币金额
	* @return string 以字符串形式返回实时的以太币金额
	*/  
	static public function convertRmbToEther($amount){
		$pricePerEther=self::realtimePrice();
		return bcdiv($amount,$pricePerEther,20);
	}
	/**  
	* 把以太币ETH金额转化成实时的等值人民币价值
	* 
	* @access public
	* @param float $amount 输入以太币ETH金额
	* @return string 以字符串形式返回实时的等值人民币金额
	*/ 
	static public function convertEtherToRmb($amount){
		$pricePerEther=self::realtimePrice();
		return bcmul($amount,$pricePerEther,20);
	}
	/**  
	* 对金额截取指定的小数位数，被截除的数值直接舍去如 self::toFloorFixed(5.7899,2) 返回的是 5.78
	* 
	* @access public
	* @param float $amount 输入需要处理的金额
	* @param float $count 指定要截取的小数位数
	* @return string 以字符串形式返回截取后的值
	*/ 
	static public function toFloorFixed($amount, $count){
		return substr(sprintf("%.".($count+1)."f",$amount),0,-1);
	}
}
// -----------------------------------------------
// EthereumApi.php -- End
// -----------------------------------------------


// echo strtolower("0x7c6c5565fc459f84e23e995e8567607e9b8cf9d4");
/*
$mockTx=json_decode('{
    "blockHash": "0xc93d90c8b6fbc573df5e685d330b144db428408f23b36090cf7373c231935dff",
    "blockNumber": "0x4c841f",
    "from": "0xf5b0a97eec56d779487d030f3a09d304265ddd46",
    "gas": "0x5208",
    "gasPrice": "0x12a05f200",
    "hash": "0xecc67f41d9f2447bb3cfe02ce166928cb4820597c6a29486bcbd7cfe9e0f2343",
    "input": "0x",
    "nonce": "0x4",
    "r": "0xbb2f3ae6bfec28000ac725801a46e3e9b52a0e8736a64904048e0bf20ee74ebc",
    "s": "0x59a59ece51f26fc2ce14908f36b6d4256347c596fe0d166ddbc7f75157bb1a6c",
    "to": "0x7c6c5565fc459f84e23e995e8567607e9b8cf9d4",
    "transactionIndex": "0x1",
    "v": "0x29",
    "value": "0x38d7ea4c68000"
}');
*/
// $api=new EthereumApi();
// $txArray=$api->txFilter(array("0x7c6c5565fc459f84e23e995e8567607e9b8cf9d4"),EthereumApi::FILTER_RECEIVER);
// print_r(json_encode($txArray));
// $newAddress=$api->generateWallet();
// print_r(json_encode($newAddress));
// print_r(EthereumApi::tx_getValue($mockTx,'rmb'));
// print_r(EthereumApi::convertRmbToEther(100));
// echo ',';
// print_r(EthereumApi::convertEtherToRmb("0.1194"));
// echo EthereumApi::realtimePrice();

// echo EthereumApi::toFloorFixed(5.6789123,3);


